﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;

using Outcoder.Collections;

using Xamarin.Forms;

namespace Outcoder.UI.Xaml
{
    public class AppBar : View
    {
	    static AppBar()
	    {
			ButtonCommandsProperty = BindableProperty.Create("ButtonCommands", typeof(IEnumerable<IUICommand>), typeof(AppBar), null, BindingMode.TwoWay, null, HandleButtonCommandsChanged);
			MenuCommandsProperty = BindableProperty.Create("MenuCommands", typeof(IEnumerable<IUICommand>), typeof(AppBar), null, BindingMode.TwoWay, null, HandleMenuCommandsChanged);
	    }

	    public AppBar()
	    {
		    var buttonCollection = (ObservableCollection<IAppBarItem>)buttons;
		    buttonCollection.CollectionChanged += OnButtonCollectionChanged;

			var menuItemCollection = (ObservableCollection<IAppBarItem>)menuItems;
			menuItemCollection.CollectionChanged += OnMenuItemCollectionChanged;
	    }

		static void HandleMenuCommandsChanged(BindableObject bindableObject, object oldvalue, object newvalue)
	    {
			var appBar = (AppBar)bindableObject;

			if (appBar == null)
			{
				return;
			}

			var oldItems = (IEnumerable<IUICommand>)oldvalue;

		    if (oldItems != null)
		    {
				appBar.DetachCollection(oldItems as INotifyCollectionChanged, 
										appBar.HandleMenuItemCollectionChanged);
		    }

			var newItems = (IEnumerable<IUICommand>)newvalue;

			var collection = appBar.menuItems;
			collection.Clear();

			if (newItems != null)
			{
				appBar.AttachCollection(newItems as INotifyCollectionChanged, 
										appBar.HandleMenuItemCollectionChanged);

				var items = appBar.AdaptItems(newItems);
				collection.AddRange(items);
			}

			appBar.OnPropertyChanged("MenuItems");
	    }

	    void DetachCollection(INotifyCollectionChanged notifyable, NotifyCollectionChangedEventHandler handler)
	    {
		    if (notifyable == null)
		    {
			    return;
		    }

		    notifyable.CollectionChanged -= handler;
	    }

		void AttachCollection(INotifyCollectionChanged notifyable, NotifyCollectionChangedEventHandler handler)
		{
			if (notifyable == null)
			{
				return;
			}

			notifyable.CollectionChanged -= handler;
			notifyable.CollectionChanged += handler;
		}

		void HandleMenuItemCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			OnMenuItemCollectionChanged(sender, e);
		}

	    void HandleButtonCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
	    {
			OnButtonCollectionChanged(sender, e);
	    }

		internal event NotifyCollectionChangedEventHandler ButtonCollectionChanged;
		internal event NotifyCollectionChangedEventHandler MenuItemCollectionChanged;

		protected virtual void OnButtonCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
	    {
		    var temp = ButtonCollectionChanged;
		    if (temp != null)
		    {
			    temp(this, e);
		    }

			var context = BindingContext;

			if (context == null)
			{
				UISynchronizationContext.Instance.InvokeWithoutBlocking(() => OnButtonCollectionChangedAux(e, context));
			}
			else
			{
				OnButtonCollectionChangedAux(e, context);
			}
	    }

	    void OnButtonCollectionChangedAux(NotifyCollectionChangedEventArgs e, object context)
	    {
			if (e.Action == NotifyCollectionChangedAction.Add)
			{
				foreach (var item in e.NewItems.OfType<BindableObject>())
				{
					OnChildAdded((Element)item);
				}
			}
			else if (e.Action == NotifyCollectionChangedAction.Reset)
			{
				if (e.OldItems != null)
				{
					foreach (var item in e.OldItems)
					{
						OnChildRemoved((Element)item);
					}
				}

				foreach (var item in buttons)
				{
					OnChildAdded((Element)item);
				}
			}
			else if (e.Action == NotifyCollectionChangedAction.Remove)
			{
				foreach (var item in e.OldItems)
				{
					OnChildRemoved((Element)item);
				}
			}
	    }

	    protected virtual void OnMenuItemCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			var temp = MenuItemCollectionChanged;
			if (temp != null)
			{
				temp(this, e);
			}
		}

		static void HandleButtonCommandsChanged(BindableObject bindableObject, object oldvalue, object newvalue)
	    {
			var appBar = (AppBar)bindableObject;

			if (appBar == null)
			{
				return;
			}

			var oldItems = (IEnumerable<IUICommand>)oldvalue;

			if (oldItems != null)
			{
				appBar.DetachCollection(oldItems as INotifyCollectionChanged, 
										appBar.HandleButtonCollectionChanged);
			}

			var newItems = (IEnumerable<IUICommand>)newvalue;

			var collection = appBar.buttons;
			collection.Clear();

			if (newItems != null)
			{
				appBar.AttachCollection(newItems as INotifyCollectionChanged, 
										appBar.HandleButtonCollectionChanged);

				var newButtons = appBar.AdaptItems(newItems);
				collection.AddRange(newButtons);
			}
			
			appBar.OnPropertyChanged("Buttons");
	    }

	    List<IAppBarItem> AdaptItems(IEnumerable<IUICommand> commands)
	    {
		    IAppBarItemFactory factory = Dependency.Resolve<IAppBarItemFactory, AppBarItemFactory>();

		    List<IAppBarItem> result = new List<IAppBarItem>();

		    foreach (var command in commands)
		    {
			    IAppBarItem item = factory.BuildItem(command);
				result.Add(item);
		    }

		    return result;
	    }

	    public static readonly BindableProperty ButtonCommandsProperty;


		public static readonly BindableProperty MenuCommandsProperty;


	    readonly ObservableList<IAppBarItem> buttons = new ObservableList<IAppBarItem>();

		public IEnumerable<IAppBarItem> Buttons
	    {
		    get
		    {
			    return buttons;
		    }
	    }

	    readonly ObservableList<IAppBarItem> menuItems = new ObservableList<IAppBarItem>();

		public IEnumerable<IAppBarItem> MenuItems
		{
			get
			{
				return menuItems;
			}
		}

    }
}
